﻿using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.ResponseCompression;
using Microsoft.CodeAnalysis;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using ZhongJia.Apartment.Core.Infrastructure.Swaggers;
using ZhongJia.Lock.Infrastructure.Data;
using ZhongJia.Lock.Infrastructure.Extensions;
using ZhongJia.Lock.Web.Filters;
using ZhongJia.Lock.Web.Settings;
using Microsoft.Extensions.Options;
using IdentityServer4.Stores;
using ZhongJia.Lock.Core.Data;
using ZhongJia.Lock.Core.Models;
using ZhangJia.Lock.Web;
using Swashbuckle.AspNetCore.Swagger;
using ZhongJia.Lock.Core.Services;
using System.Text;

namespace ZhongJia.Lock.Web.Extensions
{
    public static class ServiceCollectionExtensions
    {
        public static IServiceCollection AddCustomizedMvc(this IServiceCollection services)
        {
            var mvcBuilder = services
                .AddMvc(options =>
                {
                    options.Filters.Add(typeof(GlobalExceptionFilter));
                })
                .AddViewLocalization()
                .AddDataAnnotationsLocalization();

            return services;
        }

        public static IServiceCollection AddCustomizedDataStore(this IServiceCollection services, IConfigurationRoot configuration)
        {
            services.AddDbContext<LockDbContext>(options =>
                options.UseSqlServer(configuration.GetConnectionString("lock"),
                    b => b.MigrationsAssembly("ZhongJia.Lock.Web")));
            return services;
        }

        /// <summary>
        /// Adds customized JSON serializer settings.
        /// </summary>
        public static IMvcCoreBuilder AddCustomJsonOptions(this IMvcCoreBuilder builder) =>
            builder.AddJsonOptions(
                options =>
                {
                    // Parse dates as DateTimeOffset values by default. You should prefer using DateTimeOffset over
                    // DateTime everywhere. Not doing so can cause problems with time-zones.
                    options.SerializerSettings.DateParseHandling = DateParseHandling.DateTimeOffset;
                    // Output enumeration values as strings in JSON.
                    options.SerializerSettings.Converters.Add(new StringEnumConverter());
                });

        /// <summary>
        /// Add cross-origin resource sharing (CORS) services and configures named CORS policies. See
        /// https://docs.asp.net/en/latest/security/cors.html
        /// </summary>
        public static IMvcCoreBuilder AddCustomCors(this IMvcCoreBuilder builder) =>
            builder.AddCors(
                options =>
                {
                    // Create named CORS policies here which you can consume using application.UseCors("PolicyName")
                    // or a [EnableCors("PolicyName")] attribute on your controller or action.
                    options.AddPolicy(
                        "AllowAny",
                        x => x
                            .AllowAnyOrigin()
                            .AllowAnyMethod()
                            .AllowAnyHeader());
                });

        public static IMvcCoreBuilder AddCustomMvcOptions(
          this IMvcCoreBuilder builder,
          IConfigurationRoot configuration,
          IHostingEnvironment hostingEnvironment) =>
          builder.AddMvcOptions(
              options =>
              {
                  var cacheProfileSettings = configuration.GetSection<CacheProfileSettings>();
                  foreach (var keyValuePair in cacheProfileSettings.CacheProfiles)
                  {
                      options.CacheProfiles.Add(keyValuePair);
                  }

                  if (hostingEnvironment.IsDevelopment())
                  {
                      // Lets you pass a format parameter into the query string to set the response type:
                      // e.g. ?format=application/json. Good for debugging.
                      options.Filters.Add(new FormatFilterAttribute());
                  }

                  // Check model state for null or invalid models and automatically return a 400 Bad Request.
                  options.Filters.Add(new ValidateModelStateAttribute());

                  // Remove string and stream output formatters. These are not useful for an API serving JSON or XML.
                  options.OutputFormatters.RemoveType<StreamOutputFormatter>();
                  options.OutputFormatters.RemoveType<StringOutputFormatter>();

                  // Returns a 406 Not Acceptable if the MIME type in the Accept HTTP header is not valid.
                  options.ReturnHttpNotAcceptable = true;
              });

        public static IServiceCollection AddCaching(this IServiceCollection services)
        {
            return services
                    .AddMemoryCache()
                    .AddDistributedMemoryCache();
        }

        public static IServiceCollection AddCustomOptions(
          this IServiceCollection services,
          IConfiguration configuration) =>
          services
            // Adds IOptions<CacheProfileSettings> to the services container.
            .Configure<CacheProfileSettings>(configuration.GetSection(nameof(CacheProfileSettings)))
            .Configure<SystemSettings>(configuration.GetSection(nameof(SystemSettings)))
                 .AddScoped(cfg => cfg.GetService<IOptionsSnapshot<SystemSettings>>().Value);

        public static IServiceCollection AddCustomVersioning(this IServiceCollection services) =>
          services.AddApiVersioning(
              options =>
              {
                  options.AssumeDefaultVersionWhenUnspecified = true;
                  options.ReportApiVersions = true;
              });

        public static IServiceCollection AddCustomResponseCompression(
           this IServiceCollection services,
           IConfigurationRoot configuration)
        {
            services
               .AddResponseCompression(
                   options =>
                   {
                       // Enable response compression over HTTPS connections.
                       options.EnableForHttps = true;
                       // Add additional MIME types (other than the built in defaults) to enable GZIP compression for.
                       var responseCompressionSettings = configuration.GetSection<ResponseCompressionSettings>(
                          nameof(ResponseCompressionSettings));
                       options.MimeTypes = ResponseCompressionDefaults
                           .MimeTypes
                           .Concat(responseCompressionSettings.MimeTypes);
                   })
               .Configure<GzipCompressionProviderOptions>(
                   options => options.Level = CompressionLevel.Optimal);

            return services;
        }

        public static IServiceCollection AddSwagger(this IServiceCollection services) =>
           services.AddSwaggerGen(
               options =>
               {
                   var assembly = typeof(Startup).GetTypeInfo().Assembly;
                   var assemblyProduct = assembly.GetCustomAttribute<AssemblyProductAttribute>().Product;
                   var assemblyDescription = assembly.GetCustomAttribute<AssemblyDescriptionAttribute>().Description;

                   options.DescribeAllEnumsAsStrings();
                   options.DescribeAllParametersInCamelCase();
                   options.DescribeStringEnumsInCamelCase();

                   // Add the XML comment file for this assembly, so it's contents can be displayed.
                   options.IncludeXmlCommentsIfExists(assembly);

                   options.OperationFilter<ApiVersionOperationFilter>();
                   options.OperationFilter<AuthorizationHeaderParameterOperationFilter>();
                   // Show an example model for JsonPatchDocument<T>.
                   options.SchemaFilter<JsonPatchDocumentSchemaFilter>();
                   // Show an example model for ModelStateDictionary.
                   options.SchemaFilter<ModelStateDictionarySchemaFilter>();

                   var provider = services.BuildServiceProvider().GetRequiredService<IApiVersionDescriptionProvider>();
                   var sbDescription = new StringBuilder();
                   foreach (var apiVersionDescription in provider.ApiVersionDescriptions)
                   {
                       var info = new Info()
                       {
                           Title = $"开锁 api(Version {apiVersionDescription.ApiVersion})",
                           Description = "嘀家开锁平台api",
                           Version = apiVersionDescription.ApiVersion.ToString()
                       };
                       options.SwaggerDoc(apiVersionDescription.GroupName, info);
                   }
               });

        public static IServiceCollection AddCustomizedService(this IServiceCollection services)
        {
            services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
            services.AddScoped(typeof(IRepositoryWithTypedId<,>), typeof(RepositoryWithTypedId<,>));
            services.AddSingleton<ITcpService, TcpService>();
            return services;
        }

        public static IServiceCollection AddCustomerIdentityServer(this IServiceCollection services, IConfigurationRoot configuration)
        {
            var connectionString = configuration.GetConnectionString("lock");
            services.AddIdentityServer()
                .AddTemporarySigningCredential()
                .AddConfigurationStore(builder =>
                {
                    builder.UseSqlServer(connectionString);
                })
                .AddOperationalStore(builder =>
                 {
                     builder.UseSqlServer(connectionString);
                 });

            services.AddTransient<IPersistedGrantStore, PersistedGrantStore>();
            return services;
        }
    }
}
